package com.xl.utils.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;

/**
 * JWT的token生成和解密的工具类（pom.xml中需要添加依赖java-JWT依赖）
 * 生成的token默认过期时间为7天
 * @author xionglin
 * @createDate 2018.11.7 01:05:20
 */
@Slf4j
public class JwtUtil {
	/**
	 * 默认的自定义的密钥
	 */
	private  static final String SECRET="xionglin";

	/**
	 * 默认的头map(线程安全)可根据需求自己设置defaultHeaderMap中alg和typ的的值；一般使用默认值
	 */
	private	static final Map<String,Object> defaultHeaderMap = Collections.synchronizedMap(new HashMap<String, Object>(3));
	//默认头设置值
	static {
		 defaultHeaderMap.put("alg", "HS256");
		 defaultHeaderMap.put("typ", "JWT");
	}

	/**
	 * 私有化构成器避免工具类被实例化
	 */
	private JwtUtil(){}


	/**
	 * 创建JwtToken
	 * @param payLoadMap
	 * @return
	 */
	public static String createJwtToken(Map<String,Object> payLoadMap){
		String JwtToken = null;
		try {
			 Builder builder = getBuilder(defaultHeaderMap, payLoadMap);
			 //加密
			 JwtToken = builder.sign(Algorithm.HMAC256(SECRET));
		} catch (IllegalArgumentException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		} catch (JWTCreationException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		}
		return JwtToken;
	}
	
	
	/**
	 * 创建JwtToken
	 * @param payLoadMap 
	 * @param secret 密钥
	 * @return
	 */
	public static  String createJwtToken(Map<String,Object> payLoadMap,String secret){
		String JwtToken = null;
		try {
			 Builder builder = getBuilder(defaultHeaderMap, payLoadMap);
			 if(secret==null){
				 //默认秘钥加密
				  JwtToken = builder.sign(Algorithm.HMAC256(SECRET));
			 }else{
				 //默认秘钥加密
				  JwtToken = builder.sign(Algorithm.HMAC256(secret));
			 }
		} catch (IllegalArgumentException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		} catch (JWTCreationException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		}
		return JwtToken;
	}
	
	/**
	 * 创建JwtToken
	 * @param headerMap
	 * @param payLoadMap
	 * @return
	 */
	public static String createJwtToken(Map<String,Object> headerMap,Map<String,Object> payLoadMap){
		String JwtToken = null;
		try {
			 Builder builder = getBuilder(headerMap, payLoadMap);
			  JwtToken = builder.sign(Algorithm.HMAC256(SECRET));
		} catch (IllegalArgumentException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getMessage(e));
		} catch (JWTCreationException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		}
		return JwtToken;
	}
	
	/**
	 * 创建JwtToken
	 * @param headerMap 
	 * @param payLoadMap 
	 * @param secret 密钥
	 * @return
	 */
	public static  String createJwtToken(Map<String,Object> headerMap,Map<String,Object> payLoadMap,String secret){
		String JwtToken = null;
		try {
			 Builder builder = getBuilder(headerMap, payLoadMap);
			 if(secret==null){
				 //用默认加密
				  JwtToken = builder.sign(Algorithm.HMAC256(SECRET));
			 }else{
				 //加密
				  JwtToken = builder.sign(Algorithm.HMAC256(secret));
			 }
		} catch (IllegalArgumentException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getMessage(e));
		} catch (JWTCreationException e) {
			log.error("createJwtToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
		}
		return JwtToken;
	}
	
	/**
	 * 验证token
	 * @param token
	 * @return
	 */
	public static Map<String,Claim> verifyToken(String token){
		JWTVerifier jwtVerifier = null;
		try {
			jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
		} catch (IllegalArgumentException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getMessage(e));
		}
		DecodedJWT verify = null;
		try {
			 verify = jwtVerifier.verify(token);
		} catch (AlgorithmMismatchException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			//throw new RuntimeException("token加密的算法和解密用的算法不一致!");
		} catch (SignatureVerificationException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			//throw new RuntimeException("token的签名无效!");
		} catch (TokenExpiredException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			//throw new RuntimeException("token令牌已过期!");
		}catch(InvalidClaimException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			//throw new RuntimeException("token中claim被修改，所以token认证失败");
		}catch(Exception e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			//throw new RuntimeException("登录凭证无效（过期），请重新登录");
		}
		return verify.getClaims();

	}

	/**
	 * 解码token
	 * @param token
	 * @return
	 */
	public static Map<String,Claim> verifyToken(String token,String secret){
		JWTVerifier jwtVerifier = null;
		try {
			if(secret==null||secret.trim().equals("")){
				jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
			}else{
				jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build();	
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
		DecodedJWT verify;
		try {
			verify = jwtVerifier.verify(token);
		} catch (AlgorithmMismatchException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			throw new RuntimeException("token加密的算法和解密用的算法不一致!");
		} catch (SignatureVerificationException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			throw new RuntimeException("token签名无效!");
		} catch (TokenExpiredException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			throw new RuntimeException("token令牌已过期!");
		}catch(InvalidClaimException e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			throw new RuntimeException("token中claim被修改，所以token认证失败");
		}catch(Exception e) {
			log.error("verifyToken  error >> ex = {}", ExceptionUtils.getStackTrace(e));
			throw new RuntimeException("登录凭证无效（过期），请重新登录");
		}
		return verify.getClaims();
	}
	
	// 为加密构建Builder对象
	private static  Builder getBuilder(Map<String,Object> headerMap,Map<String,Object> payLoadMap){
		/**  Payload 默认给出的几个claim
			"iss" => "http://example.org",   #非必须。issuer 请求实体，可以是发起请求的用户的信息，也可是jwt的签发者。
			"iat" => 1356999524,                #非必须。issued at。 token创建时间，unix时间戳格式
			"exp" => "1548333419",            #非必须。expire 指定token的生命周期。unix时间戳格式
			"aud" => "http://example.com",   #非必须。接收该JWT的一方。
			"sub" => "jrocket@example.com",  #非必须。该JWT所面向的用户
			"nbf" => 1357000000,   # 非必须。not before。如果当前时间在nbf里的时间之前，则Token不被接受；一般都会留一些余地，比如几分钟。
			"jti" => '222we',     # 非必须。JWT ID。针对当前token的唯一标识
		*/
		if(headerMap.size()<=0||payLoadMap.size()<=0){
			return null;
		}
		Builder builder = JWT.create();
		builder.withHeader(headerMap);
		//签发时间
		builder.withIssuedAt(new Date());
		//默认过期的时间为7天
		builder.withExpiresAt(getDate(7));
		Set<Entry<String, Object>> entrySet = payLoadMap.entrySet();
		for (Entry<String, Object> entry : entrySet) {
			if("exp".equals(entry.getKey())){
				//过期时间(几天过期)会覆盖默认的过期时间
				builder.withExpiresAt(getDate(Integer.parseInt(String.valueOf(entry.getValue()))));
			}else if("iss".equals(entry.getKey())){
				//签发人
				builder.withIssuer(entry.getValue().toString());
			}else if("subject".equals(entry.getKey())){
				//主题
				builder.withSubject(entry.getValue().toString());
			}else if("JWTId".equals(entry.getKey())){
				//JWTToken的id，针对当前token的唯一标识
				builder.withJWTId(entry.getValue().toString());
			}else{
				//自定义部分（键和值都是String）
				builder.withClaim(entry.getKey(), entry.getValue().toString());
			}
		}
		return builder;
	}



	/**
	 * 获取用户名从token中
	 */
	public static String getUsernameFromToken(String token) {
		return JWT.decode(token).getSubject();
	}

	/**
	 * 获取jwt颁发者
	 */
	public static Date getIssuerFromToken(String token) {
		return JWT.decode(token).getIssuedAt();
	}

	/**
	 * 获取jwt颁发时间
	 */
	public static Date getIssuedAtDateFromToken(String token) {
		return JWT.decode(token).getIssuedAt();
	}

	/**
	 * 获取jwt的生效时间
	 */
	public static Date getNotBeforeDateFromToken(String token) {
		return JWT.decode(token).getNotBefore();
	}

	/**
	 * 获取jwt到期时间(过期时间)
	 */
	public static Date getExpirationDateFromToken(String token) {
		return JWT.decode(token).getExpiresAt();
	}

	/**
	 * 获取jwt接收者()
	 */
	public static List<String> getAudienceFromToken(String token) {
		return JWT.decode(token).getAudience();
	}


	/**
	 * 获取jwt的ID
	 */
	public static String getIdFromToken(String token) {
		return JWT.decode(token).getId();
	}


	/**
	 * 判断token是不是有效的(当前时间在生效时间和过期时间之间就是有效的)
	 * @param token jwtToken
	 */
	public static boolean tokenIsEffective(String token) {
		Date startDate = null;
		if (getNotBeforeDateFromToken(token) == null){
			//token生成的时候没有设置生效日期，默认有效开始时间是颁发时间
			startDate = getIssuedAtDateFromToken(token);
		}else {
			//token生成的时候制定生效日期
			startDate = getNotBeforeDateFromToken(token);
		}
		return startDate.before(new Date()) && getExpirationDateFromToken(token).after(new Date());
	}


	// 获取(days)天后时间
	private static Date getDate(int days){
		return new Date(System.currentTimeMillis()+(days*24*60*60*1000));
	}

	//测试
	public static void main(String[] args) {
		Map<String, Object> payLoadMap = Collections.synchronizedMap(new HashMap<>(8));
		//1天过期
		payLoadMap.put("exp", 1);
		payLoadMap.put("iss", "xionglin");
		//自定义claim  测试1
		payLoadMap.put("", 1);
		//自定义claim
		payLoadMap.put("测试2", 2);
		//自定义claim
		payLoadMap.put("测试3", "测试3");
		//payLoadMap.put("JWTId", "1");
		String token = JwtUtil.createJwtToken(payLoadMap);
		System.out.println("token="+token);
		Map<String, Claim> verifyToken = JwtUtil.verifyToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyIiOiIxIiwi5rWL6K-VMyI6Iua1i-ivlTMiLCLmtYvor5UyIjoiMiIsImlzcyI6Inhpb25nbGluIiwiZXhwIjoxNTk3ODkxNTk4LCJpYXQiOjE1OTc4MDUxOTh9.33rRj26OszCDsZbQ6axOiulAG01D37AT03b3J7r8flA");
	/*	Set<Entry<String, Claim>> entrySet = verifyToken.entrySet();
		for (Entry<String, Claim> entry : entrySet) {
			Claim value = entry.getValue();
			System.out.println(entry.getKey()+"<----->"+value.asString()+"<--->"+value.asDate());
		}*/

        verifyToken.forEach((k,v)->{
			System.out.println(k+"<----->"+v.asString()+"<--->"+ format(v.asDate()));
		});
	}
	private static  String format(Date data){
	    if(data == null){
	        return null;
        }
	    return  new SimpleDateFormat("yyyy-MM-dd mm:HH:ss").format(data);
    }
}
